/**
 * 文件下载器类 - 提供高效、可靠的文件下载功能
 * 
 * 核心特性:
 * - 支持分块下载，优化大文件下载性能
 * - 智能下载策略：根据文件大小自动选择单线程或线程池下载
 * - 实时进度跟踪与更新
 * - 支持暂停、恢复和取消下载操作
 * - 事件驱动架构，提供丰富的下载状态通知
 * - 自动处理错误和重试机制
 * - 可自定义UI组件和进度展示
 * 
 * 主要组件:
 * - StateManager: 管理下载状态和进度
 * - UIController: 控制下载UI元素和交互
 * - ThreadPool: 优化大文件下载的线程池实现
 */

import { request, RequestOptions } from '../http/request';
import { EventEmitter } from '../common/event-emitter';
import { resolveTarget } from '../ui/helpers';
import { DownloadStateManager, StateEvent, DownloadStatus } from './downloader/state-manager';
import { DownloadUIController } from './downloader/ui-controller';
import { ProgressElementInterface } from '../ui/progress/ProgressElementInterface';
import { SpanProgress } from '../ui/progress/SpanProgress';
import { ThreadPool, ThreadPoolEvent } from '../thread/thread-pool';

/**
 * 下载事件枚举
 * 定义下载过程中可订阅的事件类型
 */
export enum DownloaderEvent {
    /** 下载开始事件 */
    Start = 't1h0.ui.downloader.event.start',
    /** 元数据获取完成事件 */
    Metadata = 't1h0.ui.downloader.event.metadata',
    /** 进度更新事件 */
    Progress = 't1h0.ui.downloader.event.progress',
    /** 下载完成事件 */
    Complete = 't1h0.ui.downloader.event.complete',
    /** 下载暂停事件 */
    Pause = 't1h0.ui.downloader.event.pause',
    /** 下载恢复事件 */
    Resume = 't1h0.ui.downloader.event.resume',
    /** 下载取消事件 */
    Cancel = 't1h0.ui.downloader.event.cancel',
    /** 下载错误事件 */
    Error = 't1h0.ui.downloader.event.error',
    /** HTTP 403错误事件 */
    Http403 = 't1h0.ui.downloader.event.http403',
}

/**
 * 文件下载器配置选项
 * @property {string} url - 下载文件的URL地址
 * @property {string} filename - 保存到本地的文件名
 * @property {string} [textName] - 下载按钮显示文本，默认为"点我下载"
 * @property {string} [authToken] - 认证令牌，用于需要授权的下载链接
 * @property {string | HTMLElement} [target] - 下载按钮挂载的目标元素或选择器，默认为document.body
 * @property {number} [chunkSize] - 分块下载大小(字节)，默认为1MB
 * @property {ProgressElementInterface} [progressElement] - 进度条元素实例，默认为SpanProgress
 */
export interface DownloaderOptions {
    /** 下载URL */
    url: string;
    /** 保存到本地的文件名 */
    filename: string;
    /** 下载按钮显示文本 */
    textName?: string;
    /** 认证令牌 */
    authToken?: string;
    /** 下载按钮挂载的目标元素或选择器 */
    target?: string | HTMLElement;
    /** 分块下载大小(字节) */
    chunkSize?: number;
    /** 进度条元素实例 */
    progressElement?: ProgressElementInterface;
    /** 进度更新间隔(毫秒) */
    progressUpdateInterval?: number;
}

/**
 * 文件下载器主类
 * 实现分块下载、进度跟踪、暂停/恢复/取消控制及用户交互功能
 * 支持事件订阅机制，可通过on/off方法监听下载相关事件
 * 
 * @example
 * ```typescript
 * // 创建下载器实例
 * const downloader = new Downloader({
 *   url: 'https://example.com/large-file.zip',
 *   filename: '安装包.zip',
 *   target: '#download-button',
 *   authToken: 'xxxxx',
 *   chunkSize: 2 * 1024 * 1024 // 2MB分块
 * });
 * 
 * // 监听下载进度事件
 * downloader.on(DownloadEvent.Progress, (data) => {
 *   console.log(`下载进度: ${data.percent}%，速度: ${formatSpeed(data.speed)}`);
 * });
 * 
 * // 监听下载完成事件
 * downloader.on(DownloadEvent.Complete, (data) => {
 *   console.log('下载完成，文件大小:', formatSize(data.blob.size));
 * });
 * ```
 */
export class Downloader extends EventEmitter {
    /** 配置选项 - 经过初始化处理的完整配置 */
    private __config: Required<DownloaderOptions>;
    /** 挂载目标元素 - 下载按钮的父容器 */
    private __target: HTMLElement;
    /** 状态管理实例 */
    private __stateManager: DownloadStateManager;
    /** 线程池实例，用于管理下载Worker */
    private __threadPool: ThreadPool | null = null;
    /** 已下载的文件块映射 - 用于按顺序存储下载的块 */
    private __chunkMap: Map<number, Blob> = new Map();
    /** 进度更新计时器ID */
    private __progressTimer: number | null = null;
    /** 已下载的文件大小 - 用于定时更新UI */
    private __downloadedSize: number = 0;

    /**
     * 对象URL
     * 用于创建下载链接的临时URL
     * @private
     * @see https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL
     */
    private __objectURL: string | null = null;

    /**
     * 创建Downloader实例
     * @param {DownloaderOptions} options - 下载器配置选项
     * @throws {Error} 当无法找到指定的挂载元素时
     */
    constructor(options: DownloaderOptions) {
        super();
        this.__config = initOptions(options);

        // 解析目标元素
        this.__target = resolveTarget(this.__config.target);

        // 初始化UI控制器
        const UIController = new DownloadUIController(
            this.__target,
            this.__config.progressElement
        );

        // 初始化状态管理器
        this.__stateManager = new DownloadStateManager(UIController);
        this.__stateManager.updateMeta('chunkSize', this.__config.chunkSize);

        this.__bindEvents();
    }

    /**
     * 绑定用户交互事件
     * 设置点击和双击事件处理函数，实现下载控制
     * @private
     */
    private __bindEvents(): void {
        /** 处理切换至取消状态时，取消下载 */
        this.__stateManager.on(StateEvent.DownloadCancel, () => {
            this.__cancel();
        })
        /** 处理切换至开始状态时，开始下载 */
        this.__stateManager.on(StateEvent.DownloadStart, () => {
            this.__downloadFile().catch(console.error);
        })
        /** 处理切换至暂停状态时，暂停下载 */
        this.__stateManager.on(StateEvent.DownloadPause, () => {
            if (this.__threadPool) {
                this.__threadPool.pause();
            }
            this.emit(DownloadStatus.Paused);
        })
        /** 处理切换至恢复状态时，恢复下载 */
        this.__stateManager.on(StateEvent.DownloadResume, () => {
            if (this.__threadPool) {
                this.__threadPool.resume();
            } else {
                this.__downloadFile().catch(console.error);
            }
        })
        /** 处理切换至完成状态时，触发完成事件 */
        this.__stateManager.on(StateEvent.DownloadComplete, () => {
            this.emit(StateEvent.DownloadComplete);
        })
    }

    /**
     * 开始文件下载流程
     * 实现完整的下载生命周期: 元数据获取 -> 分块下载 -> 进度更新 -> 完成处理
     * @async
     * @private
     * @returns {Promise<void>}
     */
    private async __downloadFile() {
        // 防止重复下载
        if (this.__stateManager.isDownloadingStatus()) return;

        // 触发下载开始事件
        this.emit(DownloaderEvent.Start, { url: this.__config.url });

        if (this.__stateManager.isIdleStatus()) {
            this.emit(StateEvent.ProgressReset);
            this.__chunkMap.clear();
        }

        this.__stateManager.setStatus(DownloadStatus.Downloading);
        this.__stateManager.createAbortController();

        try {
            // 如果是初始状态，需要获取文件大小
            let totalSize = this.__stateManager.getMeta('totalSize');
            if (!totalSize) {
                totalSize = await this.__fetchSize();
                this.__stateManager.updateMeta('totalSize', totalSize);
            }
            this.__stateManager.updateProgress(0);

            // 3. 下载文件内容
            if (totalSize > 200 * 1024 * 1024) {
                await this.__downloadWithThreadPool();
            } else {
                await this.__downloadWithSingleThread();
            }

            // 4. 如果未取消，处理下载完成
            if (!this.__stateManager.isCancelledStatus()) {
                this.__handleDownloadComplete();
            }

        } catch (error: unknown) {
            // 错误处理(取消状态不视为错误)
            if (!this.__stateManager.isCancelledStatus()) {
                this.emit(StateEvent.ProgressReset);
                const errorMessage = error instanceof Error ? error.message : String(error);
                const errorStatus = (error as { status?: number })?.status || 500;
                console.warn('下载文件失败:', errorMessage);
                this.emit(DownloaderEvent.Error, {
                    type: 'DOWNLOAD_ERROR',
                    message: errorMessage,
                    status: errorStatus
                });
            }
        } finally {
            // 清理状态(取消状态不重置为Idle)
            if (!this.__stateManager.isCancelledStatus()) {
                this.emit(StateEvent.ProgressReset);
            }
        }
    }

    /**
     * 取消当前下载
     * 中止请求、清理资源并重置状态
     */
    private __cancel(): void {
        // 设置取消状态
        this.emit(StateEvent.DownloadCancel);

        // 取消线程池中的所有任务
        if (this.__threadPool) {
            this.__threadPool.cancel();
            this.__cleanupThreadPool();
        }

        // 中止请求
        this.__stateManager.abortDownload();

        // 清理资源
        this.__chunkMap.clear();
        if (this.__objectURL) {
            window.URL.revokeObjectURL(this.__objectURL);
        }
        this.__objectURL = null;

        // 清除进度查询计时器
        if (this.__progressTimer) {
            clearTimeout(this.__progressTimer);
            this.__progressTimer = null;
        }
        this.__downloadedSize = 0;
        // 重置进度
        this.__stateManager.emit(StateEvent.ProgressReset);
    }

    /**
     * 更新最新下载大小，但不立即更新UI
     * @private
     * @param downloadedSize - 已下载的字节数
     */
    private __updateTotalProgress(downloadedSize: number): void {
        if (!this.__stateManager.isDownloadingStatus()) {
            return;
        }

        // 只更新最新下载大小，不立即更新UI
        this.__downloadedSize += downloadedSize;
        // 立即更新进度
        // this.__stateManager.updateProgress(this.__downloadedSize);
    }


    /**
     * 分块下载文件内容 - 使用线程池实现并发下载
     * 支持断点续传、暂停/恢复和取消功能
     * @async
     * @private
     */
    private __downloadWithThreadPool(): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            try {
                // 清理之前可能存在的线程池
                this.__cleanupThreadPool();

                // 清除可能存在的计时器
                if (this.__progressTimer) {
                    clearTimeout(this.__progressTimer);
                    this.__progressTimer = null;
                }

                const totalSize = this.__stateManager.getMeta('totalSize');
                const chunkSize = this.__config.chunkSize;
                const chunkCount = Math.ceil(totalSize / chunkSize);
                this.__stateManager.updateMeta('chunkCount', chunkCount);

                // 创建线程池实例
                this.__threadPool = new ThreadPool(
                    new URL('./downloader/download.worker.ts', import.meta.url).href,
                    {
                        taskType: 'io',
                    }
                );

                // 提交所有分块下载任务
                const chunks = this.__chunks();

                // 不再需要主动查询进度，已改为事件监听
                // 初始化进度为0
                this.__stateManager.updateProgress(0);

                // 监听线程池事件
                this.__threadPool.on(ThreadPoolEvent.TaskComplete, (event: { taskId: unknown; data: { chunkIndex: number; blob: Blob } }) => {
                    const { data } = event;
                    const { chunkIndex, blob } = data;

                    // 存储下载的块
                    this.__chunkMap.set(chunkIndex, blob);

                    // 检查是否所有块都已完成
                    if (this.__chunkMap.size === chunkCount) {
                        // 立即更新一次进度
                        this.__stateManager.updateProgress(this.__downloadedSize);
                        resolve();
                    }
                });

                // 监听进度更新事件
                this.__threadPool.on(ThreadPoolEvent.TaskProgress, (event: { taskType: 'batch' | 'task'; data: { downloaded: number; } }) => {
                    // 只处理总任务进度
                    if (event.taskType === 'batch') {
                        const { data: { downloaded } } = event;

                        // 更新总体进度
                        this.__updateTotalProgress(downloaded);
                    }
                });

                // 启动进度定时器
                this.__startProgressTimer();

                // 设置错误事件监听器
                this.__threadPool.on(ThreadPoolEvent.TaskError, (event: { taskId: unknown; error: Error }) => {
                    const { taskId, error } = event;
                    console.error(`Task ${taskId} failed:`, error);
                    this.emit(DownloaderEvent.Error, {
                        type: 'TASK_ERROR',
                        message: error.message,
                        taskId
                    });
                    // 如果有任务失败且超过重试次数，整体下载失败
                    reject(error);
                });

                // 设置所有任务完成事件监听器
                this.__threadPool.on(ThreadPoolEvent.AllTasksComplete, () => {
                    // 清除进度查询计时器
                    if (this.__progressTimer) {
                        clearTimeout(this.__progressTimer);
                        this.__progressTimer = null;
                    }

                    if (this.__chunkMap.size === chunkCount) {
                        resolve();
                    } else {
                        reject(new Error('Some chunks failed to download after maximum retries'));
                    }
                    this.__cleanupThreadPool();
                });

                // 批量提交分块任务
                const batchSize = 5;
                this.__threadPool.setBatchSize(batchSize);
                const taskBatches = [];

                // 将分块任务分组
                for (let i = 0; i < chunks.length; i += batchSize) {
                    const batch = chunks.slice(i, i + batchSize).map((chunk) => ({
                        data: {
                            url: this.__config.url,
                            chunk: { start: chunk.start, end: chunk.end, index: chunk.index },
                            headers: this.__config.authToken ? { 'authToken': this.__config.authToken } : {},
                        },
                        resolve: () => {
                            console.log(`任务${chunk.index}完成`);
                        },
                        reject: (error: any) => {
                            console.error(`任务${chunk.index}失败:`, error);
                        }
                    }));
                    taskBatches.push(batch);
                }

                // 提交所有批次任务
                taskBatches.forEach((batch) => {
                    this.__threadPool?.addBatchTasks(batch);
                });

                // 进度将通过事件监听器自动更新
            } catch (error) {
                this.__cleanupThreadPool();
                reject(error);
            }
        });
    }

    /**
     * 合并下载的文件块
     * 直接从__chunkMap中获取并按索引排序
     * @private
     * @returns 排序后的文件块数组
     */
    private __mergeChunks(): Blob[] {
        // 获取所有索引并排序
        const sortedIndices = Array.from(this.__chunkMap.keys()).sort((a, b) => a - b);
        // 按排序后的索引获取块
        return sortedIndices.map(index => this.__chunkMap.get(index)) as Blob[];
    }

    private __chunks() {
        const totalSize = this.__stateManager.getMeta('totalSize');
        const chunkSize = this.__config.chunkSize;
        const chunkCount = Math.ceil(totalSize / chunkSize);
        // 提交所有分块下载任务
        const chunks: { start: number; end: number; index: number }[] = [];
        for (let i = 0; i < chunkCount; i++) {
            const start = i * chunkSize;
            const end = Math.min(start + chunkSize - 1, totalSize - 1);
            chunks.push({ start, end, index: i });
        }
        return chunks;
    }

    /**
     * 清理线程池资源
     * @private
     */
    private __cleanupThreadPool(): void {
        if (this.__threadPool) {
            this.__threadPool.destroy();
            this.__threadPool = null;
        }
    }

    /**
     * 启动进度更新定时器
     * @private
     */
    private __startProgressTimer(): void {
        if (this.__progressTimer) {
            return;
        }

        this.__progressTimer = window.setInterval(() => {
            if (this.__downloadedSize > 0 && this.__stateManager.isDownloadingStatus()) {
                // 使用最新下载大小更新UI
                this.__stateManager.updateProgress(this.__downloadedSize);

                const stateProgress = this.__stateManager.getProgress();
                const totalSize = this.__stateManager.getMeta('totalSize');
                const chunkCount = this.__stateManager.getMeta('chunkCount');
                const completedChunks = this.__chunkMap.size;
                const progress = totalSize > 0 ? (stateProgress.downloadedSize / totalSize) * 100 : 0;

                // 触发进度更新事件
                this.emit(DownloaderEvent.Progress, {
                    progress,
                    downloadedSize: stateProgress.downloadedSize,
                    totalSize,
                    speed: stateProgress.speed,
                    completedChunks,
                    chunkCount
                });
            }
        }, this.__config.progressUpdateInterval);
    }

    /**
     * 根据文件大小下载文件
     * 小于或等于分片大小：直接下载
     * 大于分片大小但小于200MB：使用Promise.all进行并发分块下载
     * @private
     * @returns {Promise<void>} 下载完成的Promise
     */
    private async __downloadWithSingleThread(): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            try {
                // 清除可能存在的计时器
                if (this.__progressTimer) {
                    clearTimeout(this.__progressTimer);
                    this.__progressTimer = null;
                }

                const totalSize = this.__stateManager.getMeta('totalSize');
                const chunkSize = this.__config.chunkSize;
                const chunkCount = Math.ceil(totalSize / chunkSize);
                this.__stateManager.updateMeta('chunkCount', chunkCount);

                // 初始化进度为0
                this.__stateManager.updateProgress(0);

                // 判断是否需要分块
                if (totalSize <= chunkSize) {
                    // 直接下载整个文件
                    const options = this.__getFetchOptions('chunk', { start: 0, end: totalSize - 1 });
                    request(options)
                        .then(async (response) => {
                            this.__handleResponseErrors(response);
                            const blob = await response.blob();

                            // 存储下载的块
                            this.__chunkMap.set(0, blob);

                            // 更新进度
                            this.__updateTotalProgress(blob.size);
                            this.__stateManager.updateProgress(blob.size);
                            resolve();
                        })
                        .catch((error) => {
                            console.error('下载失败:', error);
                            this.emit(DownloaderEvent.Error, {
                                type: 'DOWNLOAD_ERROR',
                                message: error.message
                            });
                            reject(error);
                        });
                } else {
                    // 创建分块数组
                    const chunks = this.__chunks();

                    // 创建所有分块下载的Promise
                    const downloadPromises = chunks.map((chunk) => {
                        const options = this.__getFetchOptions('chunk', chunk);
                        return request(options)
                            .then(async (response) => {
                                this.__handleResponseErrors(response);
                                const blob = await response.blob();

                                // 存储下载的块
                                this.__chunkMap.set(chunk.index, blob);
                                this.__updateTotalProgress(blob.size);

                                return { chunkIndex: chunk.index, blob };
                            })
                            .catch((error) => {
                                console.error(`分块${chunk.index}下载失败:`, error);
                                throw error;
                            });
                    });

                    // 并行下载所有分块 (已在各分块回调中更新进度和存储块)
                    Promise.all(downloadPromises)
                        .then(() => {
                            // 所有分块下载完成，无需额外处理
                            resolve();
                        })
                        .catch((error) => {
                            console.error('分块下载失败:', error);
                            this.emit(DownloaderEvent.Error, {
                                type: 'DOWNLOAD_ERROR',
                                message: error.message
                            });
                            reject(error);
                        });
                }

                // 启动进度定时器
                this.__startProgressTimer();
            } catch (error) {
                console.error('下载初始化失败:', error);
                reject(error);
            }
        });
    }

    /**
     * 处理下载完成逻辑
     * 委托给状态管理器处理文件块和URL管理，UI控制器处理界面更新
     * @private
     */
    private __handleDownloadComplete(): void {
        if (this.__objectURL) {
            window.URL.revokeObjectURL(this.__objectURL);
        }

        // 获取所有分块信息并合并
        const fullBlob = new Blob(this.__mergeChunks());
        this.__objectURL = window.URL.createObjectURL(fullBlob);

        // 触发完成事件
        this.emit(DownloaderEvent.Complete, {
            blob: fullBlob,
            url: this.__objectURL
        });

        // 创建并触发下载链接
        const a = document.createElement('a');
        a.href = this.__objectURL;
        a.download = this.__config.filename;
        a.style.display = 'none';
        this.__target.appendChild(a);
        a.click();

        // 清理资源
        window.URL.revokeObjectURL(this.__objectURL);
        this.__objectURL = null;
        this.__target.removeChild(a);

        // 重置状态
        this.__stateManager.emit(StateEvent.DownloadComplete);
        this.__chunkMap.clear();
    }

    /**
     * 获取文件大小
     * @private
     * @returns {Promise<number>} 文件大小（字节）
     */
    private async __fetchSize() {
        // 1. 获取文件元数据(HEAD请求)
        // 对于metadata请求，我们使用GET方法
        const options = this.__getFetchOptions('metadata');
        options.method = 'GET';
        const initRes = await request(options);
        this.__handleResponseErrors(initRes);

        // 2. 解析Content-Length获取文件大小
        const contentLength = initRes.headers.get('Content-Length');
        const totalSize = contentLength ? parseInt(contentLength) : 0;

        // 触发元数据事件
        this.emit(DownloaderEvent.Metadata, {
            size: totalSize,
            type: initRes.headers.get('Content-Type')
        });
        return totalSize;
    }

    /**
     * 处理HTTP响应错误
     * 检查响应状态码，对403等特殊状态进行处理
     * @param {Response} response - fetch响应对象
     * @private
     * @throws {Error} 当响应状态码异常时抛出错误
     */
    private __handleResponseErrors(response: Response): void {
        // 403错误特殊处理
        if (response.status === 403) {
            this.emit(DownloaderEvent.Http403, {
                type: 'AUTH_FAILED',
                message: response.statusText,
                response: response
            });
            throw new Error('无访问权限，请检查令牌或联系管理员');
        }

        // 其他错误状态
        if (!response.ok) {
            throw new Error(`${response.status} ${response.statusText}`);
        }
    }

    /**
     * 获取fetch请求选项
     * 根据请求类型和数据生成请求配置
     * @param {string} type - 请求类型，'chunk'或'metadata'，'chunk'选项已废弃，仅用于内部调用
     * @param {any} [data] - 可选的请求数据，如分块范围
     * @private
     * @returns {RequestOptions} 标准化的请求配置对象
     */
    private __getFetchOptions(type: 'chunk' | 'metadata', data?: any) {
        const options: RequestOptions = {
            url: this.__config.url,
            timeout: Number.MAX_SAFE_INTEGER,
            returnResponse: true,
            signal: this.__stateManager.getAbortController()?.signal,
        }
        options.headers = {};
        if (this.__config.authToken) {
            options.headers['Authorization'] = this.__config.authToken;
        }
        if (type === 'chunk') {
            options.method = 'GET';
            options.headers['Range'] = `bytes=${data.start}-${data.end}`;
        }
        if (type === 'metadata') {
            options.method = 'HEAD';
        }
        return options;
    }
}

/**
 * 初始化配置选项
 * 将用户提供的选项与默认值合并，确保所有必要配置项都存在
 * @param {DownloaderOptions} options - 用户提供的配置选项
 * @returns {Required<DownloaderOptions>} 标准化后的完整配置
 */
function initOptions(options: DownloaderOptions): Required<DownloaderOptions> {
    return {
        url: options.url,
        filename: options.filename,
        textName: options.textName || '点我下载',
        authToken: options.authToken || '',
        target: options.target || 'body',
        chunkSize: options.chunkSize || 1024 * 1024, // 默认1MB分块
        progressElement: options.progressElement || new SpanProgress({
            text: options.textName || '点我下载',
            textTemplate: '{text}',
        }),
        progressUpdateInterval: options.progressUpdateInterval || 500,
    };
}

export default Downloader;